#!/usr/bin/python
#  This is part of Scopia Visual Framework
#
#  This module implements 3D Object loading from out private xml 
#  format class and functions
#
#  Copyright (C) 2008  Scopia Visual Interfaces Systems 
#                      http://www.scopia.es
#
#  author: Alejandro Conty
#  contributors:
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.


from xml.dom import minidom
import math

class object3d_t(object):
    def __init__(self, file):
        "Loads an object from an xml file"
        print 'loading 3d obj', file
        self.parseObject(minidom.parse(file).documentElement)
        self.buildEdges(20, 0.001)

    def draw(self, c):
        "Draws the geometry in the current openGL context"
        if self.texture: c.startTexture( self.texture )
        c.startTriangles()
        for f in self.faces:
            for v in f:
                index, tex = v
                c.setColor( * self.vertices[index][0] )
                c.texCoord( tex )
                c.vertex( * self.vertices[index][1] )
        c.endTriangles()
        if self.texture: c.endTexture( self.texture )
        #self.drawEdges(c)

    def drawEdges(self, c):
        "Draws glowing lines in the edges of the object"
        c.ghosting(True)
        for a, b in self.edges:
            c.glowLine(a, b, (1, 1, 1, 0.2))
        c.ghosting(False)

    def parsePoint( self, node ):
        "Creates a 3D point from the attributes in an xml node"
        p = [0, 0, 0]
        if node.attributes.has_key('x'): p[0] = float(node.attributes['x'].value)
        if node.attributes.has_key('y'): p[1] = float(node.attributes['y'].value)
        if node.attributes.has_key('z'): p[2] = float(node.attributes['z'].value)
        return tuple(p)
    
    def parseColor( self, node ):
        "Creates a color from the attributes in an xml node"
        p = [0, 0, 0]
        if node.attributes.has_key('r'): p[0] = float(node.attributes['r'].value)
        if node.attributes.has_key('g'): p[1] = float(node.attributes['g'].value)
        if node.attributes.has_key('b'): p[2] = float(node.attributes['b'].value)
        return tuple(p)

    def parsePoints( self, node ):
        "Creates the point list from child nodes in an xml node"
        color = (1, 1, 1)
        self.vertices = []
        for c in node.childNodes:
            if c.nodeType == c.ELEMENT_NODE:
                if c.tagName == 'c': color = self.parseColor( c )
                elif c.tagName == 'p':
                    self.vertices.append( (color, self.parsePoint(c)) )
    
    def parseFace( self, node ):
        "Creates a face as [ ( vertexid, (u, v) ), ( vertexid, (u, v) ), ( vertexid, (u, v) ) ] from an xml node"
        f = []
        for c in node.childNodes:
            if c.nodeType == c.ELEMENT_NODE:
                if c.attributes.has_key('idx'): idx = int(c.attributes['idx'].value)
                else: idx = 0
                if c.attributes.has_key('u'): u = float(c.attributes['u'].value)
                else: u = 0.0
                if c.attributes.has_key('v'): v = 1.0 - float(c.attributes['v'].value)
                else: v = 0.0
                f.append( ( idx, (u, v) ) )
        return tuple( f )
    
    def parseFaces( self, node ):
        "Creates the face list from the child nodes in an xml node"
        self.faces = []
        for c in node.childNodes:
            if c.nodeType == c.ELEMENT_NODE:
                if c.tagName == 'face':
                    self.faces.append( self.parseFace( c ) )
    
    def parseObject( self, node ):
        "Does all the parsing from an xml tree"
        if node.attributes.has_key('texture'): self.texture = node.attributes['texture'].value
        else: self.texture = None
        for c in node.childNodes:
            if c.nodeType == c.ELEMENT_NODE:
                if c.tagName == 'points': self.parsePoints( c )
                elif c.tagName == 'faces': self.parseFaces( c )

    def normal( self, face ):
        "Returns the normal for the given face"
        a =  self.vertices[ face[0][0] ][1]
        b =  self.vertices[ face[1][0] ][1]
        c =  self.vertices[ face[2][0] ][1]
        N = normalizev3( crossv3( subv3(b, a), subv3(c, a) ) )
        return N
        
    def buildEdges( self, angle, offset ):
        """Creates the edge list from those from the geometry whose angle is bigger 
           than the specified.

                angle       Minimum angle for an edge to be used
                offset      Displacement applied to the edges so they don't overlap
                            with the faces """
        cosa = math.cos( angle * math.pi / 180.0 )
        candidates = {}
        for f in self.faces:
            normal = self.normal( f )
            for i in xrange(3):
                edge = (f[i][0], f[(i+1)%3][0])
                if edge[1] < edge[0]: edge = (edge[1], edge[0])
                if candidates.has_key( edge ):
                    if dotv3(normal, candidates[edge]) > cosa: del candidates[ edge ]
                    else: candidates[edge] = addv3( candidates[edge], normal) # y no lo normalizo porque no me sale de los cojones
                else: candidates[ edge ] = normal
        self.edges = [ ( addv3byf(self.vertices[e[0]][1], N, offset), 
                                         addv3byf(self.vertices[e[1]][1], N, offset) ) for e, N in candidates.items()]

# JUST FOR FUN: compare the previous function to its equivalent in my old C++ code

#void triObject_t::buildEdges(float angle, float offset)
#{
#   float cosa = cos( angle * M_PI / 180.0f);
#   map<int, map<int, TVector3f> > candidates; 
#   foreach(i , vector<face_t>, *faces )
#   {
#       int a = i->a, b = i->b;
#       if(b<a) swap(a,b);
#       TVector3f *found = includes(candidates, a, b);
#       if(found)
#       {
#           if( (i->normal * *found) > cosa) 
#           {
#               candidates[a].erase(b);
#               if(candidates[a].empty()) candidates.erase(a);
#           }
#           else *found += i->normal;
#       }
#       else candidates[a][b] = i->normal;
#
#       a = i->b; b = i->c;
#       if(b<a) swap(a,b);
#       found = includes(candidates, a, b);
#       if(found)
#       {
#           if( (i->normal * *found) > cosa) 
#           {
#               candidates[a].erase(b);
#               if(candidates[a].empty()) candidates.erase(a);
#           }
#           else *found += i->normal;
#       }
#       else candidates[a][b] = i->normal;
#       
#       a = i->c; b = i->a;
#       if(b<a) swap(a,b);
#       found = includes(candidates, a, b);
#       if(found)
#       {
#           if( (i->normal * *found) > cosa) 
#           {
#               candidates[a].erase(b);
#               if(candidates[a].empty()) candidates.erase(a);
#           }
#           else *found += i->normal;
#       }
#       else candidates[a][b] = i->normal;
#   }
#   for(map<int, map<int, TVector3f> >::iterator i= candidates.begin(); i!= candidates.end();++i)
#       for(map<int, TVector3f>::iterator j= i->second.begin(); j!= i->second.end();++j)
#       {
#           int a = i->first, b = j->first;
#           TVector3f normal = j->second;
#           normal.normalize();
#           edges.push_back(pair<TVertex3f, TVertex3f>( 
#                       (*points)[a] + normal * offset,
#                       (*points)[b] + normal * offset));
#       }
#}
